chdkfandomcom-20200222-history
DryOS Porting
DryOS Porting = OS Switch = Since late 2007 Digic-based cameras are shipped using Canon's own Operating System named DryOS. It replaces VxWorks from Wind River which has been used before on Digic2 and some Digic3 cameras. DryOS had existed before and was in use in other Canon hardware, such as digital video cameras and high-end webcams. The reason for the switch were most probably royalties Canon had to pay for VxWorks. = Cameras = As of now, these cameras use DryOS. * A720IS * A650IS * SX100IS * S5IS * G9 An indicator for DryOS is that the "Firm Update" menu entry appears if a file named *.fi2 is put on the Card instead *.fir (as this was the extension of VxWorks firmwares) = Reverse Engineering the Firmware = A decent disassembler is necessary to figure out what the firmware does. It is assumed that IDA Pro will be used. There is already an article on loading a powershot-firmware into IDA (Loading dump to IDA). The steps to examine the unknown firmware are basically the same. # Load firmware into IDA using the correct memory offset. # Disassemble code. # Locate functions and name them properly. # Figure out #* how to call firmware-functions from a C program #* what the camera does when it starts and what it initializes #* how to hook own tasks into the system # Write a test program to check the discoveries using the bootdisk-method. # Figure out how to enable the "Firm Update"-Menu entry (which does exist) to get an alternative to the bootdisk-method. Load into IDA and Disassemble code First I recommend reading Loading dump to IDA but don't follow the description just yet since some things are different. To start with a new firmware you do: # select "new disassembly" selecting "binary/raw File" # choose your dump, keep "analysis options" unchecked (defaults are ok). # select processor "ARM" # keep "start anlysis now" checked. # create a ROM section (PowerShot A-series, S/SD/G-Series start address is 0xFF810000) is this a typo? On the S5 1.01b firmware it's 0xFF800000, my IDA doesn't like 0xFF810000 The dump should be loaded to the address it was dumped from. For the reason why the IDA refuses to load whole dump read a little bit below. #* Rom Start address: 0xFFC00000 #* Rom size: 0x003FFFFF This is a little odd since the dump is actually one byte larger. But since 0xFFFFFFFF - which would be the last byte of the firmware - is a reserved address in IDA we can't use it and have to chop off the last byte from the dump. #* Loading address: 0xFFC00000 #* Loading size: 0x003FFFFF #* Press and discover the next odd thing. Although instructed to create a ROM Segment from 0xFFC00000 to 0xFFFFFFFE the segment's size is only one byte. Since the Dump is loaded completely we simply have to grow the ROM section. To do this select edit->Segments->Edit Segment and set the "End address" from 0xFFC00001 to 0xFFFFFFFE. # Since the OS is different, we can't use the old PowerShot signatures-file, so we Skip this step. The DryOS signature file has been created. So, you can apply appropriate .sig file. # Now Apply the CHDK.idc. It will not do everything it did on a VxWorks-Firmware but it disassembles most of the code. There is CHDK.idc adapted for DryOS. Note that after running CHDK.idc the entry-code which started at the very beginning of the Firmware will not be disassembled. To get this chunk, you need to unassign (key 'u') the string which is at offset +4 ("gaonisoyP") since the "P" is part of the next instruction and IDA won't disassemble assigned memory locations. After that you can set the cursor to the first byte of the firmware and press 'c' to create the code at this location. # Fix missing code. IDA stops disassembling when it thinks a function has ended. Unfortunately it thinks wrong sometimes. One particular reason to end disassembling is a BL-command. BL is "Branch with Link". It jumps to a subroutine and returns execution afterwards. In theory it is possible that a called subroutine does not return, but in the PowerShot-Firmware this is 99% not the case. So we need to fix that behaviour. Save the following code as 'code_fix.idc' and run it in IDA. It will look for BL''s preceding unknown sections and disassemble it. #include static findCode_fix(sb, se) { auto a, c, w, d, b; c = 0; for (a=sb; a 0xf8) || (d < 0x06) ){ // make sure, distance is some reasonable value (i.e. not to large) Message("Jump (%x) found at %x\n", d, a); MakeCode(a); c = c+1; } } } Message( "Code found %d times\n", c); } static main() { auto sb, se, b; Message("*** START OF ANALYSIS ***\n"); sb = MinEA(); se = MaxEA(); Message("Searching for code...\n"); MakeCode(sb); findCode_fix(sb, se); Message("Please wait...\n"); Wait(); Message("*** END OF ANALYSIS ***\n"); } What you get by now is far from perfect. A lot of things need adjustment. Typical things that need to be fixed are * Subroutines that are declared shorter than they are. You will get this one very often. A subroutine is recognized, gets a large comment saying ' S U B R O U T I N E ''' and a few lines of code later there's another comment saying End of function ... although it is clearly not the end. To fix this skip forward in the code until the actual end of the subroutine. Remember the address right after the end of the function, put the cursor back into the function code, press -p to edit the function and set the End address to the noted address. Correct lengths of subroutines are necessary to get proper xref-trees. * Code isn't disassembled at all. This happens when IDA stopped processing a function or didn't find a reference to a function at all. Just put the cursor on the first unknown byte and press either 'c' to create code that is meant to belong to another subroutine or press 'p' to create a new subroutine. If you're unsure try 'c' first. If the disassembled code looks like a subroutine, you can still press 'p'. * incorrect ASCII strings. It sometimes happens that the start of a string isn't recognized. This can be confusing since references to this string aren't resolved correctly. In that case put the cursor on the first character of the string and press 'a'. The complete string will be labeled and xref'd correctly. Locate and name functions Since most of the code is now disassembled we can now start to give it proper names or - if that's not possible - at least something more meaningful than sub_FFDF448C. GrAnd has partial signatures and some idc's for processing DryOS dumps in IDA they can be found here http://chdk.setepontos.com/index.php/topic,234.msg1688.html#msg1688 Assert The easiest thing to start with is the assert-function. Throughout the code there are things that may never fail. If the do something has gone so terribly wrong that a generic error-handler is called and the running function is aborted. That generic error-handler is Assert. For debugging-purposes Assert writes the name of the source-file as well as the line-number somewhere. So whenever we see something like CMP R1, #0 ADREQ =aSourceFile ; "SourceFile.c" MOVEQ R1, #0x7f BLEQ Assert We know (at least) that this code comes from a file named "SourceFile.c" and that the condition that jumps to the Assert-function is something fatal and we don't need to investigate that. We can also name that function to something somewhat meaningful. I used the prefix 'u' for 'unknown' or 'unsure' followed by the name of the sourcefile an (optionally) a number. So this function would be called "uSourceFile1". Whenever it is called from somewhere else, IDA will show its name and we know we've already seen it and will be given a clue what it might do. To find a function, in that case "Assert" we use the Strings window. It should have been populated by IDA with the strings in the Dump. Click the Strings Windows and press -t to search for a string. Enter "assert" and click "OK". The cursor jumps to the next occurrence. Press -t to search for the next match. The exact string we are looking for is \nAssert: File %s Line %d\n. If you found it, doubleclick the line in the Strings Window and the Disassembler view will scroll to the position in the firmware. If you found the right location you will see (addresses may differ): ROM:FFC0C0C8 aAssertFileSLin DCB 0xA ; DATA XREF: sub_FFC0C098+14 ROM:FFC0C0C8 DCB "Assert: File %s Line %d",0xA,0 The IDA-View now shows the position in the Firmware that contains that string. The comment tells us, where this string is referenced (DATA XREF: sub_FFC0C098+14). Doubleclick on "sub_..." to jump to the actual code. You will see (again, addresses may differ) ROM:FFC0C098 sub_FFC0C098 ; CODE XREF: sub_FFC05088+34j ROM:FFC0C098 ; sub_FFC057B0+30p ... ROM:FFC0C098 LDR R2, =0x1B08 ROM:FFC0C09C LDR R2, R2 ROM:FFC0C0A0 CMP R2, #0 ROM:FFC0C0A4 MOVEQ R2, R1 ROM:FFC0C0A8 MOVEQ R1, R0 ROM:FFC0C0AC ADREQ R0, aAssertFileSLin ; "\nAssert: File %s Line %d\n" ROM:FFC0C0B0 BEQ sub_FFC00EE8 ROM:FFC0C0B4 BXNE R2 ROM:FFC0C0B4 ; End of function sub_FFC0C098 We can see that this function prepares the String "Assert: File Line " and jumps somewhere else. I tried to follow what it does, but it was to confusing and I was just satisfied with the amount of information the repeated assertations give me. Since we found this function, we name it properly. Make sure the cursor is in the function code and press -p. This opens the "Edit function" dialog. In the field "Name of function" replace "sub_FFC0C098" with "Assert" and click ok. Every reference to this function will be named "Assert" and is therefore easily understandable. RegisterEventProcedure Many functions are named and registered by a function called RegisterEventProcedure and a few other, partly derived functions. It usually works like this: # LDR R0, =FunctionAddress # ADR R1, aFunctionName # BL RegisterEventProcedure By knowing the address of RegisterEventProcedure it's pretty easy to spot registration-functions that do nothing but registering a bunch of other functions. First we look for RegisterEventProcedure. As with Assert we salvage debug-information to discover and name functions. In this case we are looking for the string "RegisterEventProcedure: INVALID_HANDLE". This is an error message the hunted function may return and a hint for us where to look. Again, use the strings Window to search for "RegisterEventProcedure: INVALID_HANDLE". If you found it, navigate to the IDA-View with the string and after that to the function referring the string. You should see: ROM:FFC0C13C sub_FFC0C13C ; CODE XREF: sub_FFC0C1D0+4�j ROM:FFC0C13C ; sub_FFC0C1D8+4�j ... ROM:FFC0C13C ROM:FFC0C13C var_18 = -0x18 ROM:FFC0C13C ROM:FFC0C13C ; FUNCTION CHUNK AT ROM:FFC0C1B0 SIZE 0000001C BYTES ROM:FFC0C13C ROM:FFC0C13C STMFD SP!, {R3-R7,LR} ROM:FFC0C140 MOV R7, R0 ROM:FFC0C144 LDR R4, =0x1B0C ROM:FFC0C148 MOV R0, #0 ROM:FFC0C14C MOV R6, R2 ROM:FFC0C150 MOV R5, R1 ROM:FFC0C154 STR R0, SP,#0x18+var_18 ROM:FFC0C158 LDR R0, R4,#8 ROM:FFC0C15C MOV R1, R7 ROM:FFC0C160 MOV R2, SP ROM:FFC0C164 BL sub_FFC1A2E4 ROM:FFC0C168 CMP R0, #7 ROM:FFC0C16C ADREQ R1, aRegistereventp ; "RegisterEventProcedure: INVALID_HANDLE" ROM:FFC0C170 BEQ loc_FFC0C198 ROM:FFC0C174 CMP R0, #0 ROM:FFC0C178 LDREQ R0, SP,#0x18+var_18 ROM:FFC0C17C BLEQ sub_FFC1979C ROM:FFC0C180 MOV R0, #8 ROM:FFC0C184 BL sub_FFC19798 ROM:FFC0C188 CMP R0, #0 ROM:FFC0C18C STR R0, SP,#0x18+var_18 ROM:FFC0C190 BNE loc_FFC0C1B0 ROM:FFC0C194 ADR R1, aRegistereven_0 ; "RegisterEventProcedure: ALLOC_ERROR" ... Now use the edit function dialog to change the name to RegisterEventProcedure. Right below RegisterEventProcedure there are two other functions which do nothing but setting R2 to 0 and branching to Reg.... Name them RegisterEventProcedure_im1 and ..._im2 since they will be called as well. Even a bit further down there is the reverse. It writes the string "UnRegisterEvntProc: " and that's just its name. So call this one UnRegisterEvntProc. The general Rule for error messages is "name_of_function: what went wrong". This rule is applied by the Canon developers and we can make heavy use of it. One more function calls RegisterEventProcedure. It is called ExportToEventProcedure and used to register the libc-functions. It is registered along with a lot of other functions. Use the String Window to find ExportToEventProcedure. It is right after "ExecuteEventProcedure" and libc-function-names like "strcpy" and "sprintf". When you found it, jump to the referencing code and you will see ROM:FFDD80B4 LDR R1, =sub_FFC0CC20 ROM:FFDD80B8 ADR R0, aExporttoeven_0 ; "ExportToEventProcedure" ROM:FFDD80BC BL sub_FFC0CC20 This is somewhat funny. ExportToEventProcedure is called to register itself. The address assigned to R1 is the function address. The address assigned to R0 is the name of the function. Click on =sub_xxxxxxxx and press -. This opens another IDA-View with the referenced code. Change the function name to ExportToEventProcedure and close the new Window. All BLs to ExportEventProcedure should now use the correct name instead of the address. Continue renaming all other functions that are registered here. The same game goes for the "PT_"-Functions. Search for "PT_GetSystemTime" to get you started. Also look for "SSAPI::" and "drysh". At the front of the "SSAPI::" functions, you'll find the name loaded into R0 and then a call to a set of common functions, the first being a "Log" function, a check for a HardwareError, and a check for LowBattery status. If you search for all calls to the "Log" function, you will be led to an enormous number of functions that you can now name. When you did all these the Firmware should be a little easier to understand. enable/disable IRQs Here's the function code that enables/disables Interrupts. I accidentially ran into these. They are named nowhere and are hard to find without knowing what to look for. ROM:FFC00578 ; S U B R O U T I N E ROM:FFC00578 ROM:FFC00578 ; Returns: R0: previous state of IRQ-Flag ROM:FFC00578 ; 0x00 => irq enabled ROM:FFC00578 ; 0x80 => irq disabled ROM:FFC00578 ROM:FFC00578 IRQdisable ; CODE XREF: sub_FFC00A0C+70�p ROM:FFC00578 ; sub_FFC00F24+8�p ... ROM:FFC00578 MRS R1, CPSR ROM:FFC0057C AND R0, R1, #0x80 ROM:FFC00580 ORR R1, R1, #0x80 ROM:FFC00584 MSR CPSR_cxsf, R1 ROM:FFC00588 RET ROM:FFC00588 ; End of function IRQdisable ROM:FFC00588 ROM:FFC0058C ROM:FFC0058C ; S U B R O U T I N E ROM:FFC0058C ROM:FFC0058C ; Input: R0: desired state of the IRQ-bit ROM:FFC0058C ; 0x00 => IRQ enabled ROM:FFC0058C ; 0x80 => IRQ disabled ROM:FFC0058C ROM:FFC0058C IRQrestore ; CODE XREF: sub_FFC00A0C+8C�p ROM:FFC0058C ; sub_FFC00F24+28�p ... ROM:FFC0058C MRS R1, CPSR ROM:FFC00590 BIC R1, R1, #0x80 ROM:FFC00594 ORR R1, R1, R0 ROM:FFC00598 MSR CPSR_cxsf, R1 ROM:FFC0059C RET ROM:FFC0059C ; End of function IRQrestore ROM:FFC0059C ROM:FFC005A0 ROM:FFC005A0 ; S U B R O U T I N E ROM:FFC005A0 ROM:FFC005A0 ROM:FFC005A0 IRQenable ; CODE XREF: sub_FFC049B0+8�p ROM:FFC005A0 ; sub_FFC049C8+8�p ... ROM:FFC005A0 MRS R1, CPSR ROM:FFC005A4 BIC R1, R1, #0x80 ROM:FFC005A8 MSR CPSR_cxsf, R1 ROM:FFC005AC RET ROM:FFC005AC ; End of function IRQenable = Short ARM ASM Overview = This is a short overview over regular ARM commands as well as some compiler-generated nastiness. Full detail is available in the ARM-Reference-guides linked in the reference-section. Common commands include * LDR Load memory into a register * STR write register-contents to memory * CMP/'CMN'/'TST' evaluate Expression and set Status-Flags accordingly * B''' Branch. Continues program execution at the given location * '''BL Branch with Link. Continues program execution at given location and stores current location in LR (Link Register). * BX Branch and change execution mode (between ARM and Thumb mode) Nearly every command can evaluate the status flags. For that a condition-suffix is added to the command. The command only executes if the condition is met. E.g. B''' means "branch", '''BCS means "branch if Carry Set" The condition-suffixes are (copy-n-paste from the Technical Reference Manual): * EQ Equal * NE Not equal * CS Unsigned higher, or same * CC Unsigned lower * MI Negative * PL Positive, or zero * VS Overflow * VC No overflow * HI Unsigned higher * LS Unsigned lower, or same * GE Greater, or equal * LT Less than * GT Greater than * LE Less than, or equal * AL Always Registers and other CPU-Specific things * R0 - R12 are general purpose 32bit wide registers. * R13, a.k.a. SP is the Stack Pointer. Although I haven't seen it being referred to as R13 in IDA. * LR is the Link Register. It is set by the BL command * PC is the Program Counter. It holds the address of the currently executed command (this is not 100% accurate, see the manual for details). * CPSR is the Current Program Status Register. It holds the Status Flags and several internal Information. * SPSR is the Saved Program Status Register. I haven't seen this in the Firmware. Specific code-constructs can be seen more than once. Here are some: * Entering and leaving subroutines. On executing BL the CPU continues execution at the location in the argument and stores the old location in the LR (Link Register). At the beginning of the subroutine you will often see a command like STMFD SP!,{R4-R7,LR}. This stores Registers R4, R5, R6, R7 as well as LR to the Stack and increases the Stack Pointer (SP). The particular Registers saved will differ depending on which will be used by the subroutine. At the end of the subroutine you will usually see something like LDMFD SP!,{R4-R7,PC}. This will take Values from the Stack and store them in R4-R7 as well as PC. Note that the value that was read from LR is stored to PC (the Program Counter) causing the execution to be continued in the calling function. Also note, that Registers other than R4-R7 will remain in the state in which the subroutine left them. Usually R0 and R1 are used to share values between caller and callee. * A modification is STMFD SP!,{R4-R7,LR} at the start and LDMFD SP!,{R4-R7,LR}; B ' at the end of a function. This subroutine restores the initial state ''including the LR and branches to another subroutine. When ' returns, it will directly return to the original caller. Referencing memory. Some usual memory-referencing methods * LDR R0, R1 loads the value of R1 to R0 * LDR R0, R1 loads the value of the address referenced by R1 to R0 * LDR R0, #1 same as above, referenced addr is R1 + 1 * LDR R0, R1, #1 same as above, increases R1 by 1 afterwards To store an immediate value in a Register you use MOV * MOV R0, #0 writes 0 to R0 Meaning of multiple Registers as Arguments * ADD R0, R1, R2 general rule: R0 is the target, R1 and R2 are operands. In that case: Add R1 and R2, store the result in R0. * ADD R0, R0, R1 Adds R1 to R0 = References = * canon-page on DRYOS * ARM7TDMI Technical Reference Manual * ARM Architecture Reference Manual